package com.jichangxiu.common.utils;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.jichangxiu.common.cache.RedisService;
import com.jichangxiu.common.constant.Constant;
import com.jichangxiu.common.constant.RedisKeys;
import com.jichangxiu.common.entity.bo.Payload;
import com.jichangxiu.common.entity.bo.SecurityUser;
import com.jichangxiu.common.exception.FrameworkException;
import com.jichangxiu.common.properties.JcxProperties;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.concurrent.TimeUnit;

@Component
public class TokenUtils {

    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;

    @Resource
    private JcxProperties jcxProperties;

    @Resource
    private RedisService redisService;

    /**
     * 登录方法
     *
     * @param userInfo 用户信息
     * @return 用户信息载体
     */
    public Payload<SecurityUser> login(Object userInfo) {
        Payload<SecurityUser> payload = createJwt(userInfo);
        createLoginInfo(payload.getUserInfo().getTenantId(), payload.getUserInfo().getUsername(), payload.getJti());
        return payload;
    }

    /**
     * 退出登录
     *
     * @param httpServletRequest 请求
     */
    public void logout(HttpServletRequest httpServletRequest) {
        destroyLoginInfo(httpServletRequest);
        destroyJwt(httpServletRequest);
    }

    /**
     * 退出登录
     */
    public void logout() {
        destroyJwt(ServletUtils.getRequest());
        destroyLoginInfo(ServletUtils.getRequest());
    }

    /**
     * 创建 JWT
     *
     * @param userInfo 用户信息
     * @return Payload 载体
     */
    public Payload<SecurityUser> createJwt(Object userInfo) {
        try {
            PrivateKey privateKey = RsaUtils.getPrivateKey("private.key");
            PublicKey publicKey = RsaUtils.getPublicKey("rsa_public.pub");
            String jwt = JwtUtils.createJwtExpireInSeconds(userInfo, privateKey, jcxProperties.getAuthProperties().getAuthExpire());
            Payload<SecurityUser> payload = JwtUtils.getInfoFromJwt(jwt, publicKey, SecurityUser.class);
            redisService.setCacheObject(RedisKeys.createLoginToJwtToken(payload.getJti()), jwt, jcxProperties.getAuthProperties().getAuthExpire(), TimeUnit.SECONDS);
            return payload;
        } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {
            throw FrameworkException.build("获取私钥公钥异常", ex);
        } catch (Exception e) {
            throw FrameworkException.build("创建 JWT 异常", e);
        }
    }

    /**
     * 销毁 jwt
     *
     * @param httpServletRequest 请求
     */
    public void destroyJwt(HttpServletRequest httpServletRequest) {
        String jti = getJti(httpServletRequest);
        if (StrUtil.isNotEmpty(jti)) {
            redisService.deleteObject(RedisKeys.createLoginToJwtToken(jti));
        }
    }

    /**
     * 销毁 jwt
     */
    public void destroyJwt() {
        destroyJwt(ServletUtils.getRequest());
    }

    /**
     * 获取登录信息
     *
     * @param tenantId 租户ID
     * @param username 用户名
     * @return jti
     */
    public String getLoginInfo(String tenantId, String username) {
        return redisService.getCacheObject(RedisKeys.createLoginToOnlineUser(tenantId + "_" + username));
    }

    /**
     * 保存登录信息
     *
     * @param username 租户ID
     * @param jti      jti
     */
    public void createLoginInfo(String tenantId, String username, String jti) {
        redisService.setCacheObject(RedisKeys.createLoginToOnlineUser(tenantId + "_" + username), jti, jcxProperties.getAuthProperties().getAuthExpire(), TimeUnit.SECONDS);
    }

    /**
     * 销毁登录信息
     *
     * @param username 用户名
     */
    public void destroyLoginInfo(String tenantId, String username) {
        redisService.deleteObject(RedisKeys.createLoginToOnlineUser(tenantId + "_" + username));
    }

    /**
     * 销毁登录信息
     *
     * @param request 请求
     */
    public void destroyLoginInfo(HttpServletRequest request) {
        SecurityUser securityUser = getSecurityUser(request);
        destroyLoginInfo(securityUser.getTenantId(), securityUser.getUsername());
    }

    /**
     * 销毁登录信息
     */
    public void destroyLoginInfo() {
        destroyLoginInfo(ServletUtils.getRequest());
    }

    /**
     * 获取请求 Jti
     *
     * @return Jti
     */
    public String getJti() {
        return getJti(ServletUtils.getRequest());
    }

    /**
     * 获取请求 Jti
     *
     * @param request 请求
     * @return Jti
     */
    public String getJti(HttpServletRequest request) {
        String jti = request.getHeader(jcxProperties.getAuthProperties().getAuthHeaderKey());
        if (jcxProperties.getAuthProperties().getAddJtiPrefix() && StrUtil.isNotEmpty(jti) && jti.startsWith(Constant.TOKEN_PREFIX)) {
            jti = jti.replace(Constant.TOKEN_PREFIX, "");
        }
        return jti;
    }

    /**
     * 获取请求 jwt
     *
     * @return jwt
     */
    public String getJwt() {
        return getJwt(ServletUtils.getRequest());
    }

    /**
     * 获取请求 jwt
     *
     * @param request 请求
     * @return jwt
     */
    public String getJwt(HttpServletRequest request) {
        return redisService.getCacheObject(RedisKeys.createLoginToJwtToken(getJti(request)));
    }

    /**
     * 获取请求 jwt
     *
     * @param jti jti
     * @return jwt
     */
    public String getJwt(String jti) {
        return redisService.getCacheObject(RedisKeys.createLoginToJwtToken(jti));
    }

    /**
     * 获取用户身份信息
     *
     * @param request 请求
     * @return 用户信息
     */
    public Payload<SecurityUser> getPayload(HttpServletRequest request) {
        // 获取请求携带的令牌
        String jwt = getJwt(request);
        if (StrUtil.isEmpty(jwt)) {
            throw FrameworkException.build("获取用户身份信息异常");
        }
        try {
            PublicKey publicKey = RsaUtils.getPublicKey("rsa_public.pub");
            return JwtUtils.getInfoFromJwt(jwt, publicKey, SecurityUser.class);
        } catch (Exception e) {
            throw FrameworkException.build("获取用户身份信息异常");
        }
    }

    /**
     * 获取用户身份信息
     *
     * @param jti jti
     * @return 用户信息
     */
    public Payload<SecurityUser> getPayload(String jti) {
        // 获取请求携带的令牌
        String jwt = getJwt(jti);
        if (StrUtil.isEmpty(jwt)) {
            throw FrameworkException.build("获取用户身份信息异常");
        }
        try {
            PublicKey publicKey = RsaUtils.getPublicKey("rsa_public.pub");
            return JwtUtils.getInfoFromJwt(jwt, publicKey, SecurityUser.class);
        } catch (Exception e) {
            throw FrameworkException.build("获取用户身份信息异常");
        }
    }

    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public Payload<SecurityUser> getPayload() {
        return getPayload(ServletUtils.getRequest());
    }

    /**
     * 获取用户身份信息
     *
     * @param request 请求
     * @return 用户信息
     */
    public SecurityUser getSecurityUser(HttpServletRequest request) {
        SecurityUser securityUser = ServletUtils.getSecurityUser();
        if (ObjectUtil.isNotEmpty(securityUser)) {
            return securityUser;
        }
        // 获取请求携带的令牌
        String jwt = getJwt(request);
        if (StrUtil.isEmpty(jwt)) {
            throw FrameworkException.build("获取用户身份信息异常");
        }
        try {
            PublicKey publicKey = RsaUtils.getPublicKey("rsa_public.pub");
            Payload<SecurityUser> payload = JwtUtils.getInfoFromJwt(jwt, publicKey, SecurityUser.class);
            verifyJwt(request);
            return payload.getUserInfo();
        } catch (Exception e) {
            throw FrameworkException.build("获取用户身份信息异常");
        }
    }

    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public SecurityUser getSecurityUser() {
        return getSecurityUser(ServletUtils.getRequest());
    }

    /**
     * 获取租户 ID
     *
     * @return 租户 ID
     */
    public String getTenantId() {
        return getSecurityUser().getTenantId();
    }

    /**
     * 获取租户 ID
     *
     * @param request 请求
     * @return 租户 ID
     */
    public String getTenantId(HttpServletRequest request) {
        return getSecurityUser(request).getTenantId();
    }

    /**
     * 验证令牌有效期
     * <p>
     * 相差不足20分钟，自动刷新缓存
     *
     * @param request 请求
     */
    public void verifyJwt(HttpServletRequest request) {
        try {
            Payload<SecurityUser> payload = getPayload(request);
            if (ObjectUtil.isNotNull(payload)) {
                long expireTime = payload.getExpiration().getTime();
                long currentTime = System.currentTimeMillis();
                if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
                    refreshJwt(request);
                }
            }
        } catch (Exception ex) {
            throw FrameworkException.build("校验用户 Token 失败");
        }
    }

    /**
     * 验证令牌有效期，相差不足20分钟，自动刷新缓存
     */
    public void verifyJwt(String jti) {
        try {
            Payload<SecurityUser> payload = getPayload(jti);
            if (ObjectUtil.isNotNull(payload)) {
                long expireTime = payload.getExpiration().getTime();
                long currentTime = System.currentTimeMillis();
                if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
                    refreshJwt(jti);
                }
            }
        } catch (Exception ex) {
            throw FrameworkException.build("校验用户 Token 失败");
        }
    }

    /**
     * 验证令牌有效期，相差不足20分钟，自动刷新缓存
     */
    public void verifyJwt() {
        verifyJwt(ServletUtils.getRequest());
    }

    /**
     * 刷新令牌有效期
     *
     * @param request 请求
     */
    public void refreshJwt(HttpServletRequest request) {
        try {
            PrivateKey privateKey = RsaUtils.getPrivateKey("private.key");
            String jti = getJti(request);
            SecurityUser securityUser = getSecurityUser(request);
            String token = JwtUtils.createJwtExpireInSeconds(securityUser, jti, privateKey, jcxProperties.getAuthProperties().getAuthExpire());
            redisService.setCacheObject(RedisKeys.createLoginToJwtToken(jti), token, jcxProperties.getAuthProperties().getAuthExpire(), TimeUnit.SECONDS);
        } catch (Exception ex) {
            throw FrameworkException.build("刷新用户 Token 失败");
        }
    }

    /**
     * 刷新令牌有效期
     *
     * @param jti jti
     */
    public void refreshJwt(String jti) {
        try {
            PrivateKey privateKey = RsaUtils.getPrivateKey("private.key");
            Payload<SecurityUser> payload = getPayload(jti);
            SecurityUser userInfo = payload.getUserInfo();
            String token = JwtUtils.createJwtExpireInSeconds(userInfo, jti, privateKey, jcxProperties.getAuthProperties().getAuthExpire());
            redisService.setCacheObject(RedisKeys.createLoginToJwtToken(jti), token, jcxProperties.getAuthProperties().getAuthExpire(), TimeUnit.SECONDS);
        } catch (Exception ex) {
            throw FrameworkException.build("刷新用户 Token 失败");
        }
    }

    /**
     * 刷新令牌有效期
     */
    public void refreshJwt() {
        refreshJwt(ServletUtils.getRequest());
    }

}
